#cython: c_string_type=unicode, c_string_encoding=utf8
'''
TODO:
    - ensure that we correctly check allocation
    - remove compat sdl usage (like SDL_SetAlpha must be replaced with sdl 1.3
      call, not 1.2)
'''

include '../../lib/sdl2.pxi'

from libc.string cimport memset, memcpy, strlen
from kivy.core.image import ImageData
from kivy.compat import PY2

cdef dict sdl2_cache = {}
cdef list sdl2_cache_order = []

cdef class _TTFContainer:
    cdef TTF_Font* font
    def __cinit__(self):
        self.font = NULL
    def __dealloc__(self):
        if self.font != NULL:
            TTF_CloseFont(self.font)
            self.font = NULL


cdef class _SurfaceContainer:
    cdef SDL_Surface* surface
    cdef int w, h

    def __cinit__(self, w, h):
        self.surface = NULL
        self.w = w
        self.h = h

    def __init__(self, w, h):
        # XXX check on OSX to see if little endian/big endian make a difference
        # here.
        self.surface = SDL_CreateRGBSurface(0,
            w, h, 32,
            0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)
        memset(self.surface.pixels, 0, w * h * 4)

    def __dealloc__(self):
        if self.surface != NULL:
            SDL_FreeSurface(self.surface)
            self.surface = NULL

    def render(self, container, text, x, y):
        cdef TTF_Font *font = _get_font(container)
        cdef SDL_Color c
        cdef SDL_Surface *st
        cdef SDL_Rect r
        cdef list color = list(container.options['color'])
        if font == NULL:
            return
        c.r = <int>(color[0] * 255)
        c.g = <int>(color[1] * 255)
        c.b = <int>(color[2] * 255)
        bytes_text = <bytes>text.encode('utf-8')
        st = TTF_RenderUTF8_Blended(font, <char *>bytes_text, c)
        if st == NULL:
            return
        r.x = x
        r.y = y
        r.w = st.w
        r.h = st.h
        SDL_SetSurfaceAlphaMod(st, 0xff)
        SDL_SetSurfaceBlendMode(st, SDL_BLENDMODE_NONE)
        SDL_BlitSurface(st, NULL, self.surface, &r)
        SDL_FreeSurface(st)

    def get_data(self):
        cdef int datalen = self.surface.w * self.surface.h * 4
        cdef bytes pixels = (<char *>self.surface.pixels)[:datalen]
        data = ImageData(self.w, self.h, 'rgba', pixels)
        return data


cdef TTF_Font *_get_font(self):
    cdef TTF_Font *fontobject = NULL
    cdef _TTFContainer ttfc
    cdef char *error
    cdef str s_error

    # fast path
    fontid = self._get_font_id()
    if fontid in sdl2_cache:
        ttfc = sdl2_cache[fontid]
        return ttfc.font

    # ensure ttf is init.
    if not TTF_WasInit():
        TTF_Init()

    # try first the file if it's a filename
    fontname = self.options['font_name_r']
    bytes_fontname = <bytes>fontname.encode('utf-8')
    ext = fontname.rsplit('.', 1)
    if len(ext) == 2:
        # try to open the fount if it has an extension
        fontobject = TTF_OpenFont(bytes_fontname,
                                  int(self.options['font_size']))

    # fallback to search a system font
    if fontobject == NULL:
        s_error = (<bytes>SDL_GetError()).encode('utf-8')
        print(s_error)
        assert(0)

    sdl2_cache[fontid] = ttfc = _TTFContainer()
    ttfc.font = fontobject
    sdl2_cache_order.append(fontid)

    # to prevent too much file open, limit the number of opened fonts to 64

    while len(sdl2_cache_order) > 64:
        popid = sdl2_cache_order.pop(0)
        ttfc = sdl2_cache[popid]
        del sdl2_cache[popid]
    ttfc = sdl2_cache[fontid]
    return ttfc.font

cdef int utf_8_getchar(const char *str, size_t n) nogil:
    cdef unsigned char c_v = 0
    if n < strlen(str):
        return -1
    c_v = str[0]
    if c_v < 128:
        return 1
    if c_v < 192:
        return 0
    if c_v < 224:
        return 2
    if c_v < 240:
        return 3
    if c_v < 248:
        return 4
    if c_v < 252:
        return 5
    if c_v < 254:
        return 6
    return 0

cdef SDL_Surface*  create_and_bilt_surface(TTF_Font *font, int w, int h, SDL_Color c, char* p_text, int[] split_s, int[] line_h, int line_num) nogil:
    cdef  int sum_h, i
    cdef  char *  pt
    cdef  char *  before_pt
    cdef SDL_Surface* surface
    cdef SDL_Surface *st
    cdef SDL_Rect r
    cdef unsigned char temp_c
    surface = SDL_CreateRGBSurface(0, w, h, 32,
            0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)
    if surface == NULL:
        return NULL
    memset(surface.pixels, 0, w * h * 4)
    pt = p_text
    before_pt = pt
    sum_h = 0
    for i in range(line_num):
        pt += split_s[i]
        temp_c = pt[0]
        pt[0] = 0
        st = TTF_RenderUTF8_Blended(font, before_pt, c)
        pt[0] = temp_c
        if st == NULL:
            return surface
        before_pt = pt
        r.x = 0
        r.y = sum_h
        r.w = st.w
        r.h = st.h
        SDL_SetSurfaceAlphaMod(st, 0xff)
        SDL_SetSurfaceBlendMode(st, SDL_BLENDMODE_NONE)
        SDL_BlitSurface(st, NULL, surface, &r)
        SDL_FreeSurface(st)
        sum_h += line_h[i]
    return surface

cdef SDL_Surface*  create_and_bilt_surface_unrestricted(TTF_Font *font, int w, int h, SDL_Color c, char* p_text) nogil:
    cdef  char *  pt
    cdef SDL_Surface* surface
    cdef SDL_Surface *st
    cdef SDL_Rect r
    surface = SDL_CreateRGBSurface(0, w, h, 32,
            0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)
    if surface == NULL:
        return NULL
    memset(surface.pixels, 0, w * h * 4)
    pt = p_text
    st = TTF_RenderUTF8_Blended(font, pt, c)
    if st == NULL:
        return surface
    r.x = 0
    r.y = 0
    r.w = st.w
    r.h = st.h
    SDL_SetSurfaceAlphaMod(st, 0xff)
    SDL_SetSurfaceBlendMode(st, SDL_BLENDMODE_NONE)
    SDL_BlitSurface(st, NULL, surface, &r)
    SDL_FreeSurface(st)
    return surface

def fast_unrestricted_layout(container, text):
    cdef TTF_Font *font = _get_font(container)
    cdef int w, h, length
    cdef SDL_Surface* surface
    cdef SDL_Color c
    cdef char* c_char
    cdef list color = list(container.options['color'])
    if isinstance(text, unicode):
        text = (<unicode>text).encode('utf8')
    c_char = text
    c.r = <int>(color[0] * 255)
    c.g = <int>(color[1] * 255)
    c.b = <int>(color[2] * 255)
    if font == NULL:
        return
    with nogil:
        TTF_SizeUTF8(font, c_char, &w, &h)
        surface = create_and_bilt_surface_unrestricted(font, w, h, c, c_char)
    if surface == NULL:
        return
    cdef int datalen = surface.w * surface.h * 4
    cdef bytes pixels = (<char *>surface.pixels)[:datalen]
    SDL_FreeSurface(surface)
    data = ImageData(w, h, 'rgba', pixels)
    return data

def fast_shorten_layout(container, text, character, text_width):
    cdef TTF_Font *font = _get_font(container)
    cdef int t_w, w, h, length, chars_size, pre_c_num
    cdef  char *  c_char
    cdef  char *  pt
    cdef  char* p_short_c = '...'
    cdef unsigned char temp_c
    cdef int cur_c_n, cur_l_s, short_w, shorted
    cdef SDL_Surface* surface
    cdef SDL_Color c
    cdef char short_line[512]
    cdef list color = list(container.options['color'])
    if isinstance(text, unicode):
        text = (<unicode>text).encode('utf8')
    if isinstance(character, unicode):
        character = (<unicode>character).encode('utf8')
    c_char = text
    t_w = text_width
    c.r = <int>(color[0] * 255)
    c.g = <int>(color[1] * 255)
    c.b = <int>(color[2] * 255)
    if font == NULL:
        return
    pt = character
    with nogil:
        TTF_SizeUTF8(font, p_short_c, &short_w, &h)
        memset(short_line, 0, sizeof(short_line))
        chars_size = strlen(c_char)
        TTF_SizeUTF8(font, pt, &w, &h)
        pre_c_num = t_w / w - 1
        cur_l_s = 0
        pt = c_char
        sum_w = 0
        cur_c_n = 0
        shorted = 0
        while chars_size > 0:
            length = utf_8_getchar(pt, chars_size)
            if length < 1:
                break
            pt += length
            cur_l_s += length
            chars_size -= length
            cur_c_n += 1
            if cur_c_n >= pre_c_num:
                temp_c = pt[0]
                pt[0] = 0
                TTF_SizeUTF8(font, c_char, &w, &h)
                pt[0] = temp_c
                if w + short_w > t_w:
                    cur_l_s -= length
                    memcpy(short_line, c_char, cur_l_s)
                    memcpy(&short_line[cur_l_s], p_short_c, strlen(p_short_c))
                    shorted = 1
                    break
        if 0 == shorted:
            TTF_SizeUTF8(font, c_char, &w, &h)
            surface = create_and_bilt_surface_unrestricted(font,w, h, c, c_char)
            t_w = w
        else:
            surface = create_and_bilt_surface_unrestricted(font,t_w, h, c, short_line)
    if surface == NULL:
        return
    cdef int datalen = surface.w * surface.h * 4
    cdef bytes pixels = (<char *>surface.pixels)[:datalen]
    SDL_FreeSurface(surface)
    data = ImageData(t_w, h, 'rgba', pixels)
    return data

def fast_multi_lines_layout(container, text, character, text_width,  height_line):
    cdef TTF_Font *font = _get_font(container)
    cdef float height_scale
    cdef int t_w, w, h, length,  chars_size, pre_c_num
    cdef  char *  c_char
    cdef  char *  pt
    cdef  char *  before_pt
    cdef unsigned char temp_c
    cdef int cur_c_n, cur_l_s, l_nums, sum_h
    cdef SDL_Surface* surface
    cdef SDL_Color c
    cdef int pre_line_num[80]
    cdef int pre_line_h[80]
    cdef list color = list(container.options['color'])
    if isinstance(text, unicode):
        text = (<unicode>text).encode('utf8')
    if isinstance(character, unicode):
        character = (<unicode>character).encode('utf8')
    c_char = text
    pt = character
    height_scale = height_line
    t_w = text_width
    c.r = <int>(color[0] * 255)
    c.g = <int>(color[1] * 255)
    c.b = <int>(color[2] * 255)
    if font == NULL:
        return
    with nogil:
        memset(pre_line_num, 0, sizeof(pre_line_num))
        memset(pre_line_h, 0, sizeof(pre_line_h))
        chars_size = strlen(c_char)
        TTF_SizeUTF8(font, pt, &w, &h)
        pre_c_num = t_w / w  -1
        l_nums = 0
        cur_l_s = 0
        pt = c_char
        before_pt = pt
        sum_h = 0
        cur_c_n = 0
        while chars_size > 0:
            length = utf_8_getchar(pt, chars_size)
            if length < 1:
                break
            pt += length
            cur_l_s += length
            chars_size -= length
            cur_c_n += 1
            if cur_c_n >= pre_c_num:
                temp_c = pt[0]
                pt[0] = 0
                TTF_SizeUTF8(font, before_pt, &w, &h)
                pt[0] = temp_c
                if w > t_w:
                    pre_line_num[l_nums] = cur_l_s - length
                    pre_line_h[l_nums] = <int>(<float>h*height_scale)
                    sum_h += pre_line_h[l_nums]
                    l_nums += 1
                    before_pt = pt - length
                    cur_l_s = length
                    cur_c_n = 1
        pre_line_num[l_nums] = cur_l_s
        pre_line_h[l_nums] = <int>(<float>h*height_scale)
        sum_h += pre_line_h[l_nums]
        l_nums += 1
        surface = create_and_bilt_surface(font, t_w, sum_h, c, c_char, pre_line_num, pre_line_h, l_nums)
    if surface == NULL:
        return
    cdef int datalen = surface.w * surface.h * 4
    cdef bytes pixels = (<char *>surface.pixels)[:datalen]
    SDL_FreeSurface(surface)
    data = ImageData(t_w, sum_h, 'rgba', pixels)
    return data

def _get_extents(container, text):
    cdef TTF_Font *font = _get_font(container)
    cdef int w, h
    if font == NULL:
        return 0, 0
    if not PY2:
        text = text.encode('utf-8')
    bytes_text = <bytes>text
    TTF_SizeUTF8(font, <char *>bytes_text, &w, &h)
    return w, h

def _get_fontdescent(container):
    return TTF_FontDescent(_get_font(container))

def _get_fontascent(container):
    return TTF_FontAscent(_get_font(container))
